package cn.itcast.algorithm;

import java.util.SortedMap;
import java.util.TreeMap;

/**
 * 一致性Hash算法 -- 不带虚拟节点
 * https://blog.csdn.net/u010558660/article/details/52767218
 * create by zhangtian3 on 2019/1/7
 */
public class ConsistentHashingWithoutVirtualNode {
    // 待加入Hash环的服务器列表
    private static String[] servers = {"192.168.0.0:1000", "192.168.0.1:1001",
            "192.168.0.2:1002", "192.168.0.3:1003",
            "192.168.0.4:1004", "192.168.0.5:1005"} ;

    // key表示服务器的Hash值，value表示服务器
    private static SortedMap<Integer, String> sortedMap = new TreeMap<>() ;

    // 初始化
    static {
        for (String server : servers) {
            int hash = getHash(server) ;
            System.out.println("[" + server + "]加入集合中, 其Hash值为" + hash);
            sortedMap.put(hash, server) ;
        }
    }

    // 得到应当路由到的节点
    private static String getServer(String key) {
        // 得到该key的Hash值
        int hash = getHash(key) ;
        // 得到大于该Hash值的所有Map
        SortedMap<Integer, String> subMap = sortedMap.tailMap(hash) ;
        if (subMap.isEmpty()) {
            // 如果没有比该key的hash值大的，则从第一个node开始
            Integer i = sortedMap.firstKey() ;
            // 返回对应的服务器
            return sortedMap.get(i) ;
        }else {
            // 第一个key就是顺时针过去离node最近的那个节点
            Integer i = subMap.firstKey() ;
            // 返回对应的服务器
            return subMap.get(i) ;
        }
    }

    // 使用FNV1_32_HASH算法计算服务器的Hash值，这里不使用重新HashCode的方法，最终效果没区别
    private static int getHash(String str) {
        final int p = 16777619 ;
        int hash = (int) 2166136261L ;

        for (int i = 0; i < str.length(); i++)
            hash = (hash ^ str.charAt(i)) * p ;

        hash += hash << 13 ;
        hash ^= hash >> 7 ;
        hash += hash << 3 ;
        hash ^= hash >> 17 ;
        hash += hash<< 5 ;

        // 如果算出来的值为负数，则取其绝对值
        if (hash < 0)
            hash = Math.abs(hash) ;

        return hash ;
    }

    public static void main(String[] args) {
        String[] keys = {"太阳", "月亮", "星星", "宇宙"};
        for (int i = 0; i < keys.length; i++) {
            System.out.println("[" + keys[i] + "]的hash值为" + getHash(keys[i])
                    + ", 被路由到结点[" + getServer(keys[i]) + "]");
        }
    }
}
